iT邦幫忙

2023 iThome 鐵人賽

DAY 25
0
Modern Web

react 學習記錄系列 第 25

[Day25]我的 react 學習記錄 - Zustand

  • 分享至 

  • xImage
  •  

這篇文章的主要內容

簡單介紹全域管理工具 Zustand


Zustand

現在 react 的相關套件裡面最使用最廣泛的全域管理工具應該是 Redux,在工作上較大的專案也都是使用 Redux 在做狀態管理,但是我想要介紹另外一個近期討論度很高的狀態管理工具,Zustand。

install

npm install zustand
or
yarn add zustand


Create

Zustand 的使用非常簡單,我們先從 zustand 裡面引入 create 來建立一個 store,用來貯存狀態跟改變狀態用的 object,object 裡面則會有狀態的 value 跟改變他的 function。

// zustand
import { create } from "zustand";

export interface CountState {
  count: number;
  increase: (by: number) => void;
  resetCount: () => void;
}

export interface NumberState {
  number: number;
  increase: (by: number) => void;
  resetNumber: () => void;
}

export const useCountStore = create<CountState>()((set) => ({
  count: 0,
  increase: (by) => set((state) => ({ count: state.count + by })),
  resetCount: () => set({ count: 0 }),
}));

export const useNumberStore = create<NumberState>()((set) => ({
  number: 0,
  increase: (by) => set((state) => ({ number: state.number + by })),
  resetNumber: () => set({ number: 0 }),
}));

這邊我建立了兩個不同的 store 並且一起做 export 的動作。

create 這個 function 接收一個 function,這個 function 會接到一個 set function,透過 set function 來更新我們的 state,透過這個 set function 來更新 state 的方式就好像我們透過 setState((state) => newState) 的方式相同,需要回傳一個新的 value。

然後在 App.tsx 裡面引入剛剛建立的兩個 store。

// zustand
import {
  useCountStore,
  useNumberStore,
  CountState,
  NumberState,
} from "./store";

type Props = {
  onClick: () => void;
  children: string;
};

function Button({ onClick, children }: Props) {
  return <button onClick={onClick}>{children}</button>;
}

function DeepChild() {
  const { count, increase, resetCount } = useCountStore<CountState>(
    (state) => state
  );
  return (
    <div>
      <h3>DeepChild</h3>
      <p>Count:{count}</p>
      <Button onClick={() => increase(1)}>increase</Button>
      <Button onClick={resetCount}>reset</Button>
    </div>
  );
}

const Child = function Child() {
  return (
    <div>
      <h2>Child</h2>
      <DeepChild />
    </div>
  );
};

function ParentOne() {
  const { count } = useCountStore<CountState>((state) => state);
  console.log("ParentOne render");
  return (
    <div style={{ border: "solid 1px black" }}>
      <h1>Here is Parent One</h1>
      <p>Count:{count}</p>
      <Child />
    </div>
  );
}
function ParentTwo() {
  console.log("ParentTwo render");
  const { number, increase, resetNumber } = useNumberStore<NumberState>(
    (state) => state
  );
  return (
    <div style={{ border: "solid 1px black" }}>
      <h1>Here is Parent Two</h1>
      <p>Number:{number}</p>
      <Button onClick={() => increase(1)}>increase</Button>
      <Button onClick={resetNumber}>reset</Button>
    </div>
  );
}

function App() {
  return (
    <div style={{ display: "flex", gap: "20px" }}>
      <ParentOne />
      <ParentTwo />
    </div>
  );
}

這樣就完成了,就是這麼簡單。

zustand-1

透過 Zustand 可以簡單的完成狀態管理,不需要使用 Provider 把元件包起來,只需要在需要的地方引入你的 store 就可以直接使用。


非同步

Zustand 裡面也可以很簡單的做到非同步的狀態更新。

在 create 的 function 裡面除了可以接到 set function 以外還可以接到另外一個 get function,set 是用來更新我們的狀態,get 則是可以在任何地方取得目前最新的狀態。

// store.ts
import { create } from "zustand";
type Data = {
  id: string;
  full_name: string;
  html_url: string;
};

export interface DataState {
  data: Data[];
  status: string;
  getData: (query: string) => void;
}
export const useDataStore = create<DataState>()((set, get) => ({
  data: [],
  status: "Idle",
  getData: async (query) => {
		set({ status: "Loading", data: [] });
    console.log("get", get());
    const res = await fetch(
      `https://api.github.com/search/repositories?q=${query}`
    );
    const data = await res.json();

    set({ status: "Success", data: data.items });
    console.log("get", get());
  },
}));

在 create 的時候建立一個 async 非同步的 function 可以在裡面做非同步的動作,這邊用 fetch github 上面的 repo 來做範例,使用 getData 的時候需要傳入一個 query 字串,然後會把資料存到 data 裡面。

並且給 state 一個狀態可以用來顯示畫面,另外可以注意一下 console.log("get", get()) 裡面的內容,都會是當前最新的狀態。

// app.tsx
import { useState } from "react";
import { useDataStore } from "./store";

function App() {
  const { data, status, getData } = useDataStore((state) => state);
  const [value, setValue] = useState("");

  function clickHandler() {
    if (value) getData(value);
  }

  return (
    <div>
      <h1>Fetch with zustand</h1>
      <input
        type="text"
        value={value}
        onChange={({ target }) => setValue(target.value)}
      />
      <button onClick={clickHandler}>Search</button>
      {status === "Idle" && <p>No Data.</p>}
      {status === "Loading" && <p>Loading...</p>}
      {status === "Success" && (
        <ul>
          {data.map((item) => (
            <li key={item.id}>
              <h2>{item.full_name}</h2>
              <p>
                Repo Url:
                <a
                  href={item.html_url}
                  target="_blank"
                  rel="noreferrer noopener"
                >
                  {item.html_url}
                </a>
              </p>
            </li>
          ))}
        </ul>
      )}
    </div>
  );
}

然後在 App 這邊簡單透過 useState 來控制 input 當用戶點擊的時候就進行搜尋的動作。

zustand-2

透過 Zustand 就可以做到把狀態跟元件分開,在狀態的檔案裡面專心處理取得資料跟更新狀態的邏輯,然後元件的地方依照不同的狀態顯示畫面。

雖然大部分的第三方狀態管理工具也可以做到相同的事情,但是 Zustand 把這個事情做到非常簡化,只要建立,然後引入就可以直接使用了。

redux 搭配 toolkit 雖然也可以讓流程簡化到非常相似,但是當遇到非同步的狀態管理時還是需要再另外仰賴 thunk 或是 sage 才可以另外做到非同步的狀態更新。


zustand document

下一篇簡單介紹非同步的狀態管理工具 react query。
如果內容有誤再麻煩大家指教,我會盡快修改。

這個系列的文章會同步更新在我個人的 Medium,歡迎大家來看看 👋👋👋
Medium


上一篇
[Day24]我的 react 學習記錄 - emotion
下一篇
[Day26]我的 react 學習記錄 - react query
系列文
react 學習記錄30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言